## 导入相关模块
import random
import pygame
import sys
from pygame.locals import *
import datetime

windows_width = 800
windows_height = 600  # 游戏窗口的大小
cell_size = 20  # 贪吃蛇身体方块大小,注意身体大小必须能被窗口长宽整除

''' #初始化区
由于我们的贪吃蛇是有大小尺寸的, 因此地图的实际尺寸是相对于贪吃蛇的大小尺寸而言的
'''
map_width = int(windows_width / cell_size)
map_height = int(windows_height / cell_size)

# 颜色定义
white = (255, 255, 255)
black = (0, 0, 0)
gray = (230, 230, 230)
dark_gray = (40, 40, 40)
DARKGreen = (0, 155, 0)
Green = (0, 255, 0)
Red = (255, 0, 0)
blue = (0, 0, 255)
dark_blue = (0, 0, 139)

FOOD_color = [(10, (255, 100, 100)), (20, (100, 255, 100)), (30, (100, 100, 255))]  # 食物颜色
FOOD_IMG = ['img/gt4.jpg', 'img/gt3.jpg', 'img/gt2.jpg', 'img/gt1.jpg']
BG_COLOR = black  # 游戏背景颜色
# BGM
# BGM = pygame.mixer.Sound("bjm1.wav")

# 定义方向
UP = 1
DOWN = 2
LEFT = 3
RIGHT = 4

HEAD = 0  # 贪吃蛇头部下标
score_add = []  # 排名
max_score = 0  # 最高分数
buff = 0  # 隐分身加成时间
mirror = 0  # 镜像加成时间
buff_list = []  # 道具
has_gate = False
bomb_list = []  # 炸弹


def running_game(screen, snake_speed_clock, num):
    global has_gate
    has_gate = False
    change_speed = 0  # 定义改变速度
    start_speed = 2  # 贪吃蛇的原始速度+2
    startx = random.randint(20, map_width - 20)  # 开始位置
    starty = random.randint(10, map_height - 10)
    snake_coords = [{'x': startx, 'y': starty},  # 初始贪吃蛇
                    {'x': startx - 1, 'y': starty},
                    {'x': startx - 2, 'y': starty}]
    direction = RIGHT  # 开始时向右移动
    food = get_random_location()  # 食物随机位置
    gate = get_random_location()  # 传送门随机位置
    sep = get_random_location()  # 隐分身道具随机位置
    bomb = get_random_location()  # 炸弹随机位置
    global buff
    global mirror
    global buff_list
    global bomb_list
    bomb_list = []
    buff_list = []
    buff = 0
    mirror = 0
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                terminate()
            elif event.type == KEYDOWN:  # and 判断是否输入了反方向
                if (event.key == K_LEFT or event.key == K_a) and direction != RIGHT and mirror == 0:
                    direction = LEFT
                elif (event.key == K_RIGHT or event.key == K_d) and direction != LEFT and mirror == 0:
                    direction = RIGHT
                elif (event.key == K_UP or event.key == K_w) and direction != DOWN and mirror == 0:
                    direction = UP
                elif (event.key == K_DOWN or event.key == K_s) and direction != UP and mirror == 0:
                    direction = DOWN
                # 镜像方向
                if (event.key == K_LEFT or event.key == K_a) and direction != LEFT and mirror > 0:
                    direction = RIGHT
                elif (event.key == K_RIGHT or event.key == K_d) and direction != RIGHT and mirror > 0:
                    direction = LEFT
                elif (event.key == K_UP or event.key == K_w) and direction != UP and mirror > 0:
                    direction = DOWN
                elif (event.key == K_DOWN or event.key == K_s) and direction != DOWN and mirror > 0:
                    direction = UP
                elif event.key == K_SPACE:
                    pause(screen)
                elif event.key == K_ESCAPE:
                    terminate()
                elif event.key == K_1:  # 按“1”加速,加速不超过20
                    if change_speed < 20:
                        change_speed += 1
                elif event.key == K_2:  # 按“2”减速，减速数不能超过加速5,且不能低于当前长度最低数字
                    if change_speed > -5 and len(snake_coords) > 8:
                        change_speed -= 1
        move_snake(direction, snake_coords)  # 移动蛇
        ret = snake_is_alive(snake_coords)
        if not ret:
            break  # 蛇跪了. 游戏结束
        # 控制速度
        if len(snake_coords) < 40:
            snake_speed = change_speed + len(snake_coords) // 2 + start_speed
            draw_speed(screen, change_speed + len(snake_coords) // 2 + start_speed)
            pygame.display.update()
        else:
            snake_speed = 10  # 长度到达40时，若一直按减速，则看到速度最小为10
            draw_speed(screen, 10)
            pygame.display.update()
            snake_speed_clock.tick(snake_speed)

        snake_is_eat_food(snake_coords, food)  # 判断蛇是否吃到食物
        screen.fill(BG_COLOR)
        if has_gate:
            snake_is_eat_food2(snake_coords, gate, screen, direction)  # 判断蛇是否吃到传送门
        if len(buff_list) > 0:
            snake_is_eat_buff(snake_coords, buff_list, direction)  # 判断蛇是否吃到道具
            draw_buff(screen, buff_list)
        if len(bomb_list) > 0:
            draw_bomb(screen, bomb_list)
        draw_grid(screen)
        draw_snake(screen, snake_coords)
        # 倒计时
        if buff > 0:
            buff -= 1
            draw_snake_copy(screen, snake_coords)
            draw_time(screen, buff, 0)
        if mirror > 0:
            mirror -= 1
            draw_time(screen, mirror, 1)

        r = random.randint(0, snake_speed * 5 + 1)

        draw_food(screen, food)
        if has_gate or r == 3:
            draw_gate(screen, gate)

        if r == 1:
            buff_list.append(sep)
            sep = get_random_location()  # 道具随机位置
        if r == 2:
            bomb_list.append(bomb)
            bomb = get_random_location()  # 炸弹随机位置

        draw_score(screen, len(snake_coords) - 3)
        pygame.display.update()
        # 控制速度
        if len(snake_coords) < 40:
            snake_speed = change_speed + len(snake_coords) // 2 + start_speed
            draw_speed(screen, change_speed + len(snake_coords) // 2 + start_speed)
            pygame.display.update()
        else:
            snake_speed = 10  # 长度到达40时，若一直按减速，则看到速度最小为10
            draw_speed(screen, 10)
            pygame.display.update()
        snake_speed_clock.tick(snake_speed)
    keep_rangking(len(snake_coords) - 4, num)
    show_gameover_info(screen, len(snake_coords) - 4, num)


# 将食物画出来
def draw_food(screen, food):
    x = food['x'] * cell_size
    y = food['y'] * cell_size
    appleRect = pygame.Rect(x, y, cell_size, cell_size)
    sk = pygame.image.load(FOOD_IMG[random.randint(0, 1)]).convert()
    screen.blit(sk, appleRect)


# 画传送门
def draw_gate(screen, food):
    global has_gate
    has_gate = True
    x = food['x'] * cell_size
    y = food['y'] * cell_size
    sk = pygame.image.load('img/gate.jpg').convert()
    appleRect = pygame.Rect(x, y, cell_size, cell_size)
    screen.blit(sk, appleRect)


# 画道具
def draw_buff(screen, buffS):
    for bu in buffS:
        x = bu['x'] * cell_size
        y = bu['y'] * cell_size
        sk = pygame.image.load('img/buff.jpg').convert()
        appleRect = pygame.Rect(x, y, cell_size, cell_size)
        screen.blit(sk, appleRect)


# 画炸弹
def draw_bomb(screen, bombS):
    for bo in bombS:
        x = bo['x'] * cell_size
        y = bo['y'] * cell_size
        sk = pygame.image.load('img/bomb.jpg').convert()
        appleRect = pygame.Rect(x, y, cell_size, cell_size)
        screen.blit(sk, appleRect)


# 将贪吃蛇画出来
def draw_snake(screen, snake_coords):
    # 头
    xh = snake_coords[0]['x'] * cell_size
    yh = snake_coords[0]['y'] * cell_size
    sk = pygame.image.load('img/head1.jpg')
    wormSegmentRect = pygame.Rect(xh, yh, cell_size, cell_size)
    screen.blit(sk, wormSegmentRect)

    for coord in snake_coords[1:]:
        x = coord['x'] * cell_size
        y = coord['y'] * cell_size
        if mirror > 0:
            sk = pygame.image.load('img/mir.jpg')
        else:
            sk = pygame.image.load('img/timg.jpg')
        wormSegmentRect = pygame.Rect(x, y, cell_size, cell_size)
        screen.blit(sk, wormSegmentRect)


# 画影分身
def draw_snake_copy(screen, snake_coords):
    # 头
    xh = windows_width - snake_coords[0]['x'] * cell_size
    yh = windows_height - snake_coords[0]['y'] * cell_size
    sk = pygame.image.load('img/head1.jpg')
    wormSegmentRect = pygame.Rect(xh, yh, cell_size, cell_size)
    screen.blit(sk, wormSegmentRect)

    for coord in snake_coords[1:]:
        x = windows_width - coord['x'] * cell_size
        y = windows_height - coord['y'] * cell_size
        sk = pygame.image.load('img/timg.jpg')
        wormSegmentRect = pygame.Rect(x, y, cell_size, cell_size)
        screen.blit(sk, wormSegmentRect)


# 传送门蛇位置处理
def draw_Gate(screen, snake_coords, direction):
    if direction == LEFT or direction == UP:
        x = random.randint(1, map_width - snake_coords[len(snake_coords) - 1]['x'])
        y = random.randint(1, map_height - snake_coords[len(snake_coords) - 1]['y'])
    if direction == RIGHT:
        x = random.randint(1, map_width - snake_coords[0]['x'])
        y = random.randint(1, map_height - snake_coords[len(snake_coords) - 1]['y'])
    if direction == DOWN:
        x = random.randint(1, map_width - snake_coords[len(snake_coords) - 1]['x'])
        y = random.randint(1, map_height - snake_coords[0]['y'])
    for coord in snake_coords:
        coord['x'] = coord['x'] + x
        coord['y'] = coord['y'] + y


# 画网格
def draw_grid(screen):
    for x in range(0, windows_width, cell_size):  # draw 水平 lines
        pygame.draw.line(screen, dark_gray, (x, 0), (x, windows_height))
    for y in range(0, windows_height, cell_size):  # draw 垂直 lines
        pygame.draw.line(screen, dark_gray, (0, y), (windows_width, y))


# 移动贪吃蛇
def move_snake(direction, snake_coords):
    if direction == UP:
        newHead = {'x': snake_coords[HEAD]['x'], 'y': snake_coords[HEAD]['y'] - 1}
    elif direction == DOWN:
        newHead = {'x': snake_coords[HEAD]['x'], 'y': snake_coords[HEAD]['y'] + 1}
    elif direction == LEFT:
        newHead = {'x': snake_coords[HEAD]['x'] - 1, 'y': snake_coords[HEAD]['y']}
    elif direction == RIGHT:
        newHead = {'x': snake_coords[HEAD]['x'] + 1, 'y': snake_coords[HEAD]['y']}
    snake_coords.insert(0, newHead)


#  吃道具音效
def eat_sound():
    sound = pygame.mixer.Sound("music/eat.wav")
    sound.play()


# 判断蛇死了没
def snake_is_alive(snake_coords):
    tag = True
    if snake_coords[HEAD]['x'] == -1 or snake_coords[HEAD]['x'] == map_width or snake_coords[HEAD]['y'] == -1 or \
            snake_coords[HEAD]['y'] == map_height:
        tag = False  # 蛇碰壁啦
    for snake_body in snake_coords[1:]:
        if snake_body['x'] == snake_coords[HEAD]['x'] and snake_body['y'] == snake_coords[HEAD]['y']:
            tag = False  # 蛇碰到自己身体啦
    for bomb in bomb_list:
        if bomb['x'] == snake_coords[HEAD]['x'] and bomb['y'] == snake_coords[HEAD]['y']:
            tag = False  # 蛇碰到炸弹啦
        if buff > 0:
            xh = map_width - snake_coords[HEAD]['x']
            yh = map_height - snake_coords[HEAD]['y']
            if bomb['x'] == xh and bomb['y'] == yh:
                tag = False  # 影子碰到炸弹啦
    return tag


# 判断贪吃蛇是否吃到食物
def snake_is_eat_food(snake_coords, food):  # 如果是列表或字典，那么函数内修改参数内容，就会影响到函数体外的对象。
    xh = map_width - snake_coords[HEAD]['x']
    yh = map_height - snake_coords[HEAD]['y']
    if snake_coords[HEAD]['x'] == food['x'] and snake_coords[HEAD]['y'] == food['y']:
        eat_sound()
        food['x'] = random.randint(0, map_width - 1)
        food['y'] = random.randint(0, map_height - 1)  # 实物位置重新设置
    # 判断分身是否吃到食物
    elif buff > 0 and xh == food['x'] and yh == food['y']:
        eat_sound()
        food['x'] = random.randint(0, map_width - 1)
        food['y'] = random.randint(0, map_height - 1)  # 实物位置重新设置
    else:
        del snake_coords[-1]  # 如果没有吃到实物, 就向前移动, 那么尾部一格删掉


# 判断贪吃蛇是否吃到道具
def snake_is_eat_buff(snake_coords, buffs, direction):
    global buff
    global mirror
    global bomb_list
    for bu in buffs:
        if snake_coords[HEAD]['x'] == bu['x'] and snake_coords[HEAD]['y'] == bu['y']:
            # 随机道具效果
            eat_sound()
            r = random.randint(0, 2)
            if r == 0:
                buff += 30
                buffs.remove(bu)
                add_snake(snake_coords, 3, direction)
                return
            elif r == 1:
                mirror += 30
                buffs.remove(bu)
                add_snake(snake_coords, 3, direction)
                return
            elif r == 2:
                bomb_list = []
                add_snake(snake_coords, 3, direction)
                return
        if buff > 0:
            xh = map_width - snake_coords[HEAD]['x']
            yh = map_height - snake_coords[HEAD]['y']
            if xh == bu['x'] and yh == bu['y']:
                eat_sound()
                # 影子吃到道具
                r = random.randint(0, 2)
                if r == 0:
                    buff += 30
                    buffs.remove(bu)
                    add_snake(snake_coords, 3, direction)
                    return
                elif r == 1:
                    mirror += 30
                    buffs.remove(bu)
                    add_snake(snake_coords, 3, direction)
                    return
                elif r == 2:
                    bomb_list = []
                    add_snake(snake_coords, 3, direction)
                    return

    return


# 判断贪吃蛇是否吃到传送门
def snake_is_eat_food2(snake_coords, food, screen, direction):  # 如果是列表或字典，那么函数内修改参数内容，就会影响到函数体外的对象。
    global has_gate
    if snake_coords[HEAD]['x'] == food['x'] and snake_coords[HEAD]['y'] == food['y']:
        eat_sound()
        has_gate = False
        food['x'] = random.randint(0, map_width - 1)
        food['y'] = random.randint(0, map_height - 1)  # 实物位置重新设置
        draw_Gate(screen, snake_coords, direction)
        add_snake(snake_coords, 2, direction)
    xh = map_width - snake_coords[HEAD]['x']
    yh = map_height - snake_coords[HEAD]['y']
    # 判断分身是否吃到传送门
    if buff > 0 and xh == food['x'] and yh == food['y']:
        eat_sound()
        has_gate = False
        food['x'] = random.randint(0, map_width - 1)
        food['y'] = random.randint(0, map_height - 1)  # 实物位置重新设置
        draw_Gate(screen, snake_coords, direction)
        add_snake(snake_coords, 2, direction)
    else:
        return
        # del snake_coords[-1]  # 如果没有吃到实物, 就向前移动, 那么尾部一格删掉


# 食物随机生成
def get_random_location():
    return {'x': random.randint(2, map_width - 2), 'y': random.randint(6, map_height - 2)}


# 开始信息显示
def show_start_info(screen):
    font1 = pygame.font.Font('ttf/myfont.ttf', 80)
    tip1 = font1.render('贪吃的二哈', True, (255, 0, 0))
    font = pygame.font.Font('ttf/myfont.ttf', 40)
    tip = font.render('按任意键开始游戏', True, (255, 0, 0))
    gamestart = pygame.image.load('img/s_2.jpg').convert()  # 图片自适应设置
    screen.blit(gamestart, (0, 0))
    screen.blit(tip1, (360, 90))
    screen.blit(tip, (330, 260))
    pygame.display.update()
    while True:  # 键盘监听事件
        for event in pygame.event.get():  # event handling loop
            if event.type == QUIT:
                terminate()  # 终止程序
            elif event.type == KEYDOWN:
                if (event.key == K_ESCAPE):  # 终止程序
                    terminate()  # 终止程序
                elif event.key == K_SPACE:
                    pause = True
                    pygame.quit()
                    quit()
                else:
                    return  # 结束此函数, 开始游戏


# 游戏结束信息显示
def show_gameover_info(screen, score, num):
    font = pygame.font.Font('ttf/myfont.ttf', 30)
    font2 = pygame.font.Font('ttf/myfont.ttf', 40)
    tip = font.render('按Q或者ESC退出游戏, 按3可以查看排行榜', True, (255, 255, 0))
    tip2 = font2.render('按任意键重新开始游戏', True, (255, 0, 255))
    # 创建一个和串窗口一样大小的图片
    start = pygame.image.load('img/o2.jpg')
    pygame.mixer.music.stop()
    sound = pygame.mixer.Sound("music/over.wav")
    sound.play()

    # 设定显示的背景图和位置
    screen.blit(start, (100, 100))
    screen.blit(tip, (140, 450))
    screen.blit(tip2, (200, 500))
    pygame.display.update()
    while True:  # 键盘监听事件
        for event in pygame.event.get():  # event handling loop
            if event.type == QUIT:
                terminate()  # 终止程序
            elif event.type == KEYDOWN:
                if event.key == K_ESCAPE or event.key == K_q:  # 终止程序
                    terminate()  # 终止程序
                elif event.key == K_3:  # 排名
                    show_ranking(screen, score, num)
                    pygame.display.update()
                else:
                    pygame.mixer.music.play()
                    return  # 结束此函数, 重新开始游戏


# 画成绩
def draw_score(screen, score):
    font = pygame.font.Font('ttf/myfont.ttf', 30)
    scoreSurf = font.render('得分: %s' % score, True, Green)
    scoreRect = scoreSurf.get_rect()
    scoreRect.topleft = (windows_width - 120, 10)
    screen.blit(scoreSurf, scoreRect)
    scoreSurf2 = font.render('最高分数: %s' % max_score, True, Green)
    scoreRect2 = scoreSurf.get_rect()
    scoreRect2.topleft = (windows_width - 160, 40)
    screen.blit(scoreSurf2, scoreRect2)


# 画倒计时
def draw_time(screen, count, type):
    font = pygame.font.Font('ttf/myfont.ttf', 30)
    if type == 0:  # 隐分身
        scoreSurf = font.render('分身模式时间剩余：%s' % count, True, Red)
        scoreRect = scoreSurf.get_rect()
        scoreRect.topleft = (windows_width - 400, 70)
    elif type == 1:  # 镜像
        scoreSurf = font.render('镜像模式时间剩余：%s' % count, True, Red)
        scoreRect = scoreSurf.get_rect()
        scoreRect.topleft = (windows_width - 400, 100)
    elif type == 2:
        scoreSurf = font.render(': %s' % count, True, Red)
        scoreRect = scoreSurf.get_rect()
        scoreRect.topleft = (windows_width - 120, 10)
    screen.blit(scoreSurf, scoreRect)


# 画速度
def draw_speed(screen, speed):
    font = pygame.font.Font('ttf/myfont.ttf', 30)
    speedSurf = font.render('速度: %s' % speed, True, Green)
    speedRect = speedSurf.get_rect()
    speedRect.topleft = (windows_width - 790, 10)
    screen.blit(speedSurf, speedRect)


def add_snake(snake_coords, count, direction):
    hx = snake_coords[0]['x']
    hy = snake_coords[0]['y']
    if direction == LEFT:
        for i in range(0, count):
            snake_coords.insert(len(snake_coords), {'x': hx + 1 + i, 'y': hy})
        return
    if direction == RIGHT:
        for i in range(0, count):
            snake_coords.insert(len(snake_coords), {'x': hx - 1 - i, 'y': hy})
        return
    if direction == UP:
        for i in range(0, count):
            snake_coords.insert(len(snake_coords), {'x': hx, 'y': hy + 1 + i})
        return
    if direction == DOWN:
        for i in range(0, count):
            snake_coords.insert(len(snake_coords), {'x': hx, 'y': hy - 1 - i})
        return


# 保存排名
def keep_rangking(score, num):
    global max_score
    if score > max_score:
        max_score = score
    now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    score_list = {}
    score_list['次数'] = num
    score_list['分数'] = score
    score_list['时间'] = now
    global score_add
    score_add.append(score_list)
    #  排序
    score_add.sort(key=takeSecond)
    print(score_add)


# 获取列表的分数
def takeSecond(elem):
    return elem['分数']


# 排名
def show_ranking(screen, score, num):
    rang = pygame.image.load('img/ph1.jpg')
    screen.blit(rang, (0, 0))
    font = pygame.font.Font('ttf/myfont.ttf', 30)
    scoreSurf = font.render('%s' % '排名    ' + '分数' + "                 " + '时间', True, Green)
    scoreRect = scoreSurf.get_rect()
    scoreRect.topleft = (windows_width - 620, 180)
    screen.blit(scoreSurf, scoreRect)
    for i in range(num):
        show_ranking_text(screen, i)


def show_ranking_text(screen, i):
    font = pygame.font.Font('ttf/myfont.ttf', 30)
    scoreSurf = font.render(
        '%s' % str(i + 1) + '         ' + str(score_add[len(score_add) - i - 1]['分数']) + "        " + (
            score_add[len(score_add) - i - 1]['时间']), True, Green)
    scoreRect = scoreSurf.get_rect()
    scoreRect.topleft = (windows_width - 620, 220 + (i * 50))
    screen.blit(scoreSurf, scoreRect)


# 程序暂停
def pause(screen):
    pygame.mixer.music.stop()
    font = pygame.font.Font('ttf/myfont.ttf', 40)
    tem = font.render('游戏暂停, 按“空格”键继续游戏~', True, (255, 255, 0))
    font2 = pygame.font.Font('ttf/myfont.ttf', 20)
    tem2 = font2.render('提示：按1可以加速，按2可以减速哟', True, Green)
    screen.blit(tem, (150, 260))
    screen.blit(tem2, (200, 360))
    pygame.display.update()
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                terminate()
            elif event.type == pygame.KEYDOWN:
                if event.key == K_SPACE:
                    pygame.mixer.music.play()
                    return


# 程序终止
def terminate():
    pygame.quit()
    sys.exit()


# 主函数
def main():
    pygame.init()  # 模块初始化
    snake_speed_clock = pygame.time.Clock()  # 创建Pygame时钟对象
    screen = pygame.display.set_mode((windows_width, windows_height), 0, 32)  #
    screen.fill(white)
    pygame.display.set_caption("Jixq 贪吃的二哈")  # 设置标题
    show_start_info(screen)  # 欢迎信息
    # BGM
    pygame.mixer.init()
    pygame.mixer.music.load("music/bjm1.wav")
    pygame.mixer.music.play()
    num = 0
    while True:
        num += 1
        running_game(screen, snake_speed_clock, num)


if __name__ == '__main__':
    main()
